﻿using ErrorOr;
using Gnivc.CarDealership.Api.Application.Abstractions.Repositories;
using Gnivc.CarDealership.Api.Application.Dtos;
using Gnivc.CarDealership.Api.Domain.Entities;
using Gnivc.CarDealership.Api.Domain.Extensions;
using Gnivc.CarDealership.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace Gnivc.CarDealership.Api.Infrastructure.Repositories;

public class VehicleRepository : IVehicleRepository
{
    private readonly IApplicationDbContext _context;

    public VehicleRepository(IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<ErrorOr<IEnumerable<VehicleDto>>> GetAvailablesAsync(
        DateTime date,
        Guid? brandId = null,
        Guid? modelId = null)
    {
        var filteredVehicles = _context.Vehicles
            .AsNoTracking()
            .Where(v => !v.IsDeleted);

        if (modelId != null)
            filteredVehicles = filteredVehicles.Where(v => v.ModelId == modelId);
        else if (brandId != null)
            filteredVehicles = filteredVehicles.Where(v => v.Model.BrandId == brandId);

        var dbQuery = filteredVehicles
            .Include(v => v.Model)
            .ThenInclude(m => m.Brand)
            .Include(v => v.Prices)
            .Where(v => !_context.Reservations
                .Any(r =>
                    !r.IsDeleted &&
                    r.VehicleId == v.Id &&
                    r.StartDate <= date &&
                    r.EndDate >= date))
            .Select(v => new VehicleDto
            {
                VehicleId = v.Id,
                BrandId = v.Model.BrandId,
                ModelId = v.Model.Id,
                BrandText = v.Model.Brand.Name,
                ModelText = v.Model.Name,
                EngineVolumeText = v.Engine.Volume.ToString(),
                EnginePowerText = v.Engine.Power.ToString(),
                EngineTypeText = v.Engine.Type.ToAttributeString(),
                AllPrices = v.Prices
                    .OrderBy(p => p.StartDate)
                    .Select(p => new PriceDto
                    {
                        PriceId = p.Id,
                        StartDate = p.StartDate,
                        EndDate = p.EndDate,
                        Value = p.Value
                    })
                    .ToList()
            });

        return await dbQuery.ToListAsync();
    }

    public async Task<ErrorOr<Vehicle?>> GetByIdAsync(Guid vehicleId)
    {
        var vehicle =  await _context.Vehicles
            .Where(v => v.Id == vehicleId && !v.IsDeleted)
            .SingleOrDefaultAsync();

        if (vehicle == null)
            return Error.NotFound();

        return vehicle;
    }

    public async Task<ErrorOr<Created>> AddAsync(Vehicle vehicle)
    {
        await _context.Vehicles.AddAsync(vehicle);
        var result = await _context.SaveChangesAsync();

        if (result < 1)
            return Error.Unexpected();

        return new Created();
    }

    public async Task<ErrorOr<Updated>> UpdateAsync(Vehicle vehicle)
    {
        _context.Vehicles.Update(vehicle);
        var result = await _context.SaveChangesAsync();

        if (result < 1)
            return Error.Unexpected();

        return new Updated();
    }

    public async Task<ErrorOr<Deleted>> DeleteAsync(Guid vehicleId)
    {
        var getResult = await GetByIdAsync(vehicleId);

        if (getResult.IsError)
            return getResult.Errors;

        getResult.Value!.IsDeleted = true;

        var updateResult = await UpdateAsync(getResult.Value);

        if (updateResult.IsError)
            return updateResult.Errors;

        return new Deleted();
    }
}
